package com.weizhu.swufer.autoconfiguration;

import cn.hutool.core.util.StrUtil;
import com.weizhu.swufer.constant.SwuferConstant;
import com.weizhu.swufer.core.annotation.SwuferFilter;
import com.weizhu.swufer.core.annotation.SwuferService;
import com.weizhu.swufer.core.entity.RpcBaseInfo;
import com.weizhu.swufer.core.filter.Filter;
import com.weizhu.swufer.core.filter.FilterManager;
import com.weizhu.swufer.core.util.ClassUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.InetAddress;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

public class RpcInfoScanner{

    private static final Logger logger = LoggerFactory.getLogger(RpcInfoScanner.class);

    private String scanPackagePath;

    private ApplicationContext applicationContext;

    private BeanFactory beanFactory;

    private List<RpcBaseInfo> rpcBaseInfoList;

    private Properties properties;

    public Properties getProperties() {
        return properties;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    public RpcInfoScanner(ApplicationContext applicationContext,BeanFactory beanFactory){
        this.applicationContext = applicationContext;
        this.beanFactory = beanFactory;
    }

    public RpcInfoScanner(){

    }

    public RpcInfoScanner(String scanPackagePath){
        this.scanPackagePath = scanPackagePath;
    }

    public void scanRpcBaseInfo(){
        this.scanRpcBaseInfo(this.scanPackagePath);
    }

    public List<RpcBaseInfo> getRpcBaseInfoList() {
        return rpcBaseInfoList;
    }


    public void readRpcBaseInfo(){
        this.rpcBaseInfoList = new ArrayList<>();
        Collection<Object> instances = applicationContext.getBeansWithAnnotation(SwuferService.class).values();
        String hostAddress = "";
        try {
            InetAddress localHost = InetAddress.getLocalHost();
            hostAddress = localHost.getHostAddress();
        }catch (Exception e){
            e.printStackTrace();
        }

        for (Object instance : instances) {
            SwuferService anno = instance.getClass().getAnnotation(SwuferService.class);
            if(anno != null){
                RpcBaseInfo rpcBaseInfo = new RpcBaseInfo();
                SwuferService rpcAnno = anno;
                Set<String> serviceNames = new HashSet<>();
                String serviceName = rpcAnno.service();
                if (StrUtil.isBlank(serviceName)) {

                    Class<?>[] interfaces = ClassUtil.getInterfacesOfClass(instance.getClass());
                    for (Class<?> interfaceClass : interfaces) {

                        String interfaceName = interfaceClass.getName();
                        if (!StrUtil.startWith(interfaceName, "org.springframework")
                                && !StrUtil.startWith(interfaceName, "java.")) {
                            serviceNames.add(interfaceName);
                        }
                    }
                }else {
                    serviceNames.add(serviceName);
                }
                for (String realServiceName : serviceNames) {
                    rpcBaseInfo.setGroup(rpcAnno.group());
                    rpcBaseInfo.setService(realServiceName);
                    rpcBaseInfo.setType(rpcAnno.type());
                    rpcBaseInfo.setVersion(rpcAnno.version());
                    rpcBaseInfo.setQps(rpcAnno.qps());
                    rpcBaseInfo.setTarget(instance);
                    rpcBaseInfo.setPort(Integer.parseInt(properties.getProperty(SwuferConstant.PROPERTIES_PORT)));
                    rpcBaseInfo.setIp(hostAddress);
                    rpcBaseInfo.setToken(anno.token().equals("")?null:anno.token());
                    rpcBaseInfo.setWeight(Integer.parseInt(properties.getProperty(SwuferConstant.PROPERTIES_WEIGHT)));
                    this.rpcBaseInfoList.add(rpcBaseInfo);
                }
            }
        }

    }

    public void scanFilter(String packageName) throws Exception {
        Set<Class<?>> filterClasses = getAnnotationClasses(packageName, SwuferFilter.class);
        FilterManager filterManager = FilterManager.getInstance();
        filterClasses.forEach(clazz->{
            Object obj = null;
            try {
                obj = clazz.newInstance();
                filterManager.registerFilter(obj);
            } catch (Exception e) {
                e.printStackTrace();
            }

        });


    }


    public void scanRpcBaseInfo(String packageName){
        this.rpcBaseInfoList = new ArrayList<>();
        String hostAddress = "";
        try {
            InetAddress localHost = InetAddress.getLocalHost();
            hostAddress = localHost.getHostAddress();
        }catch (Exception e){
            e.printStackTrace();
        }
        try{
            if (packageName == null){
                return;
            }
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            String name = packageName.replaceAll("\\.","/");
            URL url = classLoader.getResource(name);
            File file = new File(url.getFile());
            File[] files = file.listFiles();
            assert files != null;
            for(File f:files){
                if(f.isDirectory()){
                    scanRpcBaseInfo(packageName+"."+f.getName());
                }else{
                    String classname = packageName + "." + f.getName().replace(".class", "");
                    Class clazz = Class.forName(classname);
                    Annotation anno = clazz.getAnnotation(SwuferService.class);
                    if(anno != null){
                        RpcBaseInfo rpcBaseInfo = new RpcBaseInfo();
                        SwuferService rpcAnno = (SwuferService) anno;
                        Object target = clazz.newInstance();
                        Set<String> serviceNames = new HashSet<>();
                        String serviceName = rpcAnno.service();
                        if (StrUtil.isBlank(serviceName)) {
                            Class<?>[] interfaces = ClassUtil.getInterfacesOfClass(clazz);
                            for (Class<?> interfaceClass : interfaces) {

                                String interfaceName = interfaceClass.getName();
                                if (!StrUtil.startWith(interfaceName, "org.springframework")
                                        && !StrUtil.startWith(interfaceName, "java.")) {
                                    serviceNames.add(interfaceName);
                                }
                            }
                        }else {
                            serviceNames.add(serviceName);
                        }
                        for (String realServiceName : serviceNames) {
                            rpcBaseInfo.setGroup(rpcAnno.group());
                            rpcBaseInfo.setService(realServiceName);
                            rpcBaseInfo.setType(rpcAnno.type());
                            rpcBaseInfo.setVersion(rpcAnno.version());
                            rpcBaseInfo.setIp(hostAddress);
                            rpcBaseInfo.setTarget(target);
                            this.rpcBaseInfoList.add(rpcBaseInfo);
                        }
                    }
                }

            }

        }catch(Exception e){
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 从包package中获取所有的Class
     *
     * @param packageName
     * @return
     */
    public Set<Class<?>> getClasses(String packageName) throws Exception {

        Set<Class<?>> classes = new HashSet<>();
        boolean recursive = true;
        String packageDirName = packageName.replace('.', '/');
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            while (dirs.hasMoreElements()) {

                URL url = dirs.nextElement();

                String protocol = url.getProtocol();

                if ("file".equals(protocol)) {

                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");

                    addClass(classes, filePath, packageName);
                } else if ("jar".equals(protocol)) {

                    JarFile jar;
                    try {

                        jar = ((JarURLConnection) url.openConnection()).getJarFile();

                        Enumeration<JarEntry> entries = jar.entries();

                        while (entries.hasMoreElements()) {

                            JarEntry entry = entries.nextElement();
                            String name = entry.getName();

                            if (name.charAt(0) == '/') {

                                name = name.substring(1);
                            }

                            if (name.startsWith(packageDirName)) {
                                int idx = name.lastIndexOf('/');

                                if (idx != -1) {

                                    packageName = name.substring(0, idx).replace('/', '.');
                                }

                                if ((idx != -1) || recursive) {

                                    if (name.endsWith(".class") && !entry.isDirectory()) {

                                        String className = name.substring(packageName.length() + 1, name.length() - 6);
                                        try {
                                            // 添加到classes
                                            classes.add(Class.forName(packageName + '.' + className));
                                        } catch (ClassNotFoundException e) {
                                            e.printStackTrace();
                                        }
                                    }
                                }
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return classes;
    }

    public void addClass(Set<Class<?>> classes, String filePath, String packageName) throws Exception {
        File[] files = new File(filePath).listFiles(file -> (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory());
        assert files != null;
        for (File file : files) {
            String fileName = file.getName();
            if (file.isFile()) {
                String classsName = fileName.substring(0, fileName.lastIndexOf("."));
                if (!packageName.isEmpty()) {
                    classsName = packageName + "." + classsName;
                }
                Class<?> aClass = Class.forName(classsName);
                classes.add(aClass);
            }

        }
    }

    public void doAddClass(Set<Class<?>> classes, final String classsName) throws Exception {
        ClassLoader classLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                return super.loadClass(name);
            }
        };
        classes.add(classLoader.loadClass(classsName));
    }


    public <A extends Annotation> Set<Class<?>> getAnnotationClasses(String packageName, Class<A> annotationClass) throws Exception {

        Set<Class<?>> controllers = new HashSet<>();
        Set<Class<?>> clsList = getClasses(packageName);
        if (clsList != null && clsList.size() > 0) {
            for (Class<?> cls : clsList) {
                if (cls.getAnnotation(annotationClass) != null) {
                    controllers.add(cls);
                }
            }
        }
        return controllers;
    }


}
